package com.qd.panda.service.score;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.db.BaseService;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.qd.common.panda.constant.PandaConstant;
import com.qd.common.panda.domain.entity.score.CarParkEvaluateDTO;
import com.qd.common.panda.domain.entity.score.CarParkScoreEvaluationLabelDTO;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.panda.mapper.score.CarParkEvaluateMapper;
import com.qd.panda.model.CarParkEvaluate;
import com.qd.panda.model.UserStrokeHistory;
import com.qd.panda.service.park.CarParkService;
import com.qd.panda.service.stroke.BigDecimalUtil;
import com.qd.panda.service.stroke.UserStrokeHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;

/**
 * 停车场评价记录
 *
 * @author sjk
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class CarParkEvaluateService extends BaseService<CarParkEvaluateMapper, CarParkEvaluate> {

    private static final String PREFIX_ID = "panda_car_park_evaluate_eid_{0}";

    private static final String PREFIX_UNIQUE_PANDA_CAR = "panda_car_park_evaluate_sid_{0}_evaluationLabel_{1}";
    private static final String PARK_ID = "park_id";
    private final RedisTemplateUtil<CarParkEvaluate> redisTemplateUtil;

    private final CarParkService carParkService;
    private final UserStrokeHistoryService userStrokeHistoryService;

    private final CarParkScoreEvaluationLabelService carParkScoreEvaluationLabelService;


    /**
     * 保存
     *
     * @param dto 页面对象
     * @return 页面对象
     */
    @Transactional(rollbackFor = Exception.class)
    public CarParkEvaluateDTO save(CarParkEvaluateDTO dto) {
        check(dto);
        final CarParkEvaluate model = dtoToModel(dto);
        try {
            if (super.save(model)) {
                avgParkScore(model.getParkId(), true);
                return modelToDto(model);
            }
            throw new ApiException("添加失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 删除，单个
     *
     * @param eid 主键
     */
    @Transactional(rollbackFor = Exception.class)
    public void removeById(String eid) {
        final CarParkEvaluate model = Optional.ofNullable(getById(eid)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (super.removeById(eid)) {
                avgParkScore(model.getParkId(), true);
                return;
            }
            throw new ApiException("删除失败");
        } finally {
            clearCache(model);
            model.freeData();
        }
    }

    /**
     * 根据传入对象构建查询参数，判断是否存在
     *
     * @param dto 参数
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExists(CarParkEvaluateDTO dto) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = createQueryWrapper(dto);
        final boolean exists = super.count(queryWrapper) > 0;
        queryWrapper.clear();
        return exists;
    }

    /**
     * 根据主键判断数据是否存在
     *
     * @param eid 主键
     * @return 存在为true
     */
    @Transactional(readOnly = true)
    public Boolean isExistsById(String eid) {
        boolean exists = false;
        if (StringUtils.hasLength(eid)) {
            final CarParkEvaluate model = getById(eid);
            exists = (null != model);
            if (exists) {
                model.freeData();
            }
        }
        return exists;
    }

    /**
     * 根据主键获取数据
     *
     * @param eid 主键
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public CarParkEvaluate getById(String eid) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, eid);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getById(eid), CarParkEvaluate.class);
    }

    /**
     * 根据主键获取数据
     *
     * @param eid 主键
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public CarParkEvaluateDTO getByIdDto(String eid) {
        return Optional.ofNullable(getById(eid)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录
     */
    @Transactional(readOnly = true)
    public CarParkEvaluate getOne(CarParkEvaluateDTO dto) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = createQueryWrapper(dto);
        CarParkEvaluate model = super.getOne(queryWrapper);
        queryWrapper.clear();
        return model;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param dto 查询参数
     * @return 单条记录，转换成页面对象
     */
    @Transactional(readOnly = true)
    public CarParkEvaluateDTO getOneDto(CarParkEvaluateDTO dto) {
        return Optional.ofNullable(getOne(dto)).map(this::modelToDto).orElse(null);
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据
     */
    @Transactional(readOnly = true)
    public List<CarParkEvaluate> getList(CarParkEvaluateDTO dto) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = createQueryWrapper(dto);
        final List<CarParkEvaluate> list = super.list(queryWrapper);
        queryWrapper.clear();
        return list;
    }

    /**
     * 根据条件查询列表数据
     *
     * @param dto 查询参数
     * @return 列表数据，转换成页面对象
     */
    @Transactional(readOnly = true)
    public List<CarParkEvaluateDTO> getListDto(CarParkEvaluateDTO dto) {
        return modelToDtoList(getList(dto));
    }

    /**
     * 分页数据
     *
     * @param page 分页对象
     * @param dto  查询参数
     * @return 分页信息
     */
    @Transactional(readOnly = true)
    public IPage<CarParkEvaluateDTO> page(Page<CarParkEvaluate> page, CarParkEvaluateDTO dto) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = createQueryWrapper(dto, "car.");
        final IPage<CarParkEvaluateDTO> iPage = baseMapper.page(page, queryWrapper);
        queryWrapper.clear();
        return iPage;
    }

    /**
     * 根据唯一性索引获得数据
     * <p>
     * param sid
     *
     * @return 单条数据
     */
    @Transactional(readOnly = true)
    public CarParkEvaluate getUniqueBySid(String sid, String evaluationLabel) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sid", sid);
        queryWrapper.eq("evaluationlabel", evaluationLabel);
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_PANDA_CAR, sid, evaluationLabel);
        final CarParkEvaluate model = redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY), () -> super.getOne(queryWrapper, false), CarParkEvaluate.class);
        queryWrapper.clear();
        return model;
    }

    /**
     * 验证唯一性索引
     *
     * @param dto 页面对象
     */
    private void checkUnique(CarParkEvaluateDTO dto) {
        CarParkEvaluate model = getUniqueBySid(dto.getSid(), dto.getEvaluationLabel());
        if (null != model) {
            checkUnique(dto, model);
        }
    }

    /**
     * 验证唯一性索引
     *
     * @param dto   页面对象
     * @param model 根据约束字段查询出来的数据库对象
     */
    private void checkUnique(CarParkEvaluateDTO dto, CarParkEvaluate model) {
        if (!StringUtils.hasLength(dto.getEid())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
        if (!dto.getEid().equals(model.getEid())) {
            throw new ApiException("数据已存在，请勿重复添加");
        }
    }

    /**
     * 清空缓存
     *
     * @param model 数据库对象
     */
    public void clearCache(CarParkEvaluate model) {
        if (null == model) {
            return;
        }
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getEid()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_UNIQUE_PANDA_CAR, model.getSid()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(CarParkService.PREFIX_P_ID, model.getParkId()));
    }

    /**
     * 数据库对象转页面对象
     *
     * @param model 数据库对象
     * @return 页面对象
     */
    public CarParkEvaluateDTO modelToDto(CarParkEvaluate model) {
        if (null == model) {
            return null;
        }
        final CarParkEvaluateDTO dto = new CarParkEvaluateDTO();
        dto.setEid(model.getEid());
        dto.setAddTime(model.getAddTime());
        dto.setScore(model.getScore());
        dto.setContent(model.getContent());
        dto.setUserId(model.getUserId());
        dto.setPlateNumber(model.getPlateNumber());
        dto.setParkId(model.getParkId());
        dto.setSid(model.getSid());
        dto.setEvaluationLabel(model.getEvaluationLabel());
        buildDisplay(dto);
        return dto;
    }

    /**
     * 页面对象转数据库对象
     *
     * @param dto 页面对象
     * @return 数据库对象
     */
    public CarParkEvaluate dtoToModel(CarParkEvaluateDTO dto) {
        if (null == dto) {
            return null;
        }
        final CarParkEvaluate model = new CarParkEvaluate();
        model.setEid(dto.getEid());
        model.setAddTime(dto.getAddTime());
        model.setScore(dto.getScore());
        model.setContent(dto.getContent());
        model.setUserId(dto.getUserId());
        model.setPlateNumber(dto.getPlateNumber());
        model.setParkId(dto.getParkId());
        model.setSid(dto.getSid());
        model.setEvaluationLabel(dto.getEvaluationLabel());
        return model;
    }

    /**
     * 码表转换
     *
     * @param dto 页面对象
     */
    public void buildDisplay(CarParkEvaluateDTO dto) {
        dto.setParkIdDisplay(carParkService.idToDisplay(dto.getParkId()));
    }

    /**
     * 数据库对象转页面对象
     *
     * @param list 数据库对象
     * @return 页面对象
     */
    public List<CarParkEvaluateDTO> modelToDtoList(List<CarParkEvaluate> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::modelToDto).collect(toList());
    }

    /**
     * 页面对象转数据库对象
     *
     * @param list 页面对象
     * @return 数据库对象
     */
    public List<CarParkEvaluate> dtoToModelList(List<CarParkEvaluateDTO> list) {
        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(this::dtoToModel).collect(toList());
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto 查询参数，不为空的属性会被自动加入
     */
    public QueryWrapper<CarParkEvaluate> createQueryWrapper(CarParkEvaluateDTO dto) {
        return createQueryWrapper(dto, "");
    }

    /**
     * 拼装查询参数，除了主键的其他字段会自动加入进来
     *
     * @param dto        查询参数，不为空的属性会被自动加入
     * @param tableAlias 表别名
     */
    public QueryWrapper<CarParkEvaluate> createQueryWrapper(CarParkEvaluateDTO dto, String tableAlias) {
        final QueryWrapper<CarParkEvaluate> queryWrapper = new QueryWrapper<>();
        if (null != dto) {
            if (StringUtils.hasLength(tableAlias)) {
                if (tableAlias.charAt(tableAlias.length() - 1) != '.') {
                    tableAlias += ".";
                }
            }
            if (null != dto.getScore()) {
                queryWrapper.eq(tableAlias + "score", dto.getScore());
            }
            if (StringUtils.hasLength(dto.getUserId())) {
                queryWrapper.eq(tableAlias + "user_id", dto.getUserId());
            }
            if (StringUtils.hasLength(dto.getPlateNumber())) {
                queryWrapper.eq(tableAlias + "plate_number", dto.getPlateNumber());
            }
            if (StringUtils.hasLength(dto.getParkId())) {
                queryWrapper.eq(tableAlias + PARK_ID, dto.getParkId());
            }
        }
        queryWrapper.orderByAsc(tableAlias + PARK_ID);
        return queryWrapper;
    }

    /**
     * 批量检查主键在数据库中是否存在
     *
     * @param ids 主键集合
     */
    public List<CarParkEvaluate> checkIds(List<String> ids) {
        if (null == ids || ids.isEmpty()) {
            throw new ApiException("ids不能为空");
        }
        final List<CarParkEvaluate> list = super.listByIds(ids);
        if (list.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(ids.toArray()));
        }
        if (ids.size() == list.size()) {
            return list;
        }
        List<String> dbIds = list.stream().map(CarParkEvaluate::getEid).collect(toList());
        final List<String> notExistsList = ids.stream().filter(id -> !dbIds.contains(id)).collect(toList());
        dbIds.clear();
        if (!notExistsList.isEmpty()) {
            throw new ApiException("ids不存在" + Arrays.toString(notExistsList.toArray()));
        }
        return list;
    }

    public String avgParkScore(String parkId) {
        return avgParkScore(parkId, false);
    }

    /**
     * 计算停车场的平均分
     *
     * @param parkId    停车场ID
     * @param calculate 是否重新计算
     * @return 平均分
     */
    public String avgParkScore(String parkId, boolean calculate) {
        String key = RedisTemplateUtil.getRedisCacheKey(CarParkService.PREFIX_ID_AVG, parkId);
        String avgStr = redisTemplateUtil.getStringRedisTemplate().opsForValue().get(key);
        if (StringUtils.hasLength(avgStr) && !calculate) {
            return avgStr;
        }
        Double avg = getBaseMapper().avgParkScore(parkId);
        if (null == avg) {
            avgStr = PandaConstant.CAR_PARK_SCORE_AVG_DEFAULT;
        } else {
            avgStr = BigDecimalUtil.buildDefaultScale(BigDecimal.valueOf(avg), 1).toPlainString();
        }
        redisTemplateUtil.getStringRedisTemplate().opsForValue().set(key, avgStr, 7, TimeUnit.DAYS);
        return avgStr;
    }

    /**
     * 获取当前停车场最后一条评论
     *
     * @param parkId 停车场ID
     * @return 针对当前停车场的最后一条评价
     */
    public String getLastContent(String parkId) {
        String key = RedisTemplateUtil.getRedisCacheKey(CarParkService.PREFIX_P_ID, parkId);

        CarParkEvaluate carParkEvaluate = redisTemplateUtil.get(new CacheKey().setKey(key), () -> {
            QueryWrapper<CarParkEvaluate> queryWrapper = new QueryWrapper<>();
            queryWrapper.select("content")
                    .eq(PARK_ID, parkId)
                    .orderByDesc("add_time")
                    .last("limit 1");
            CarParkEvaluate parkEvaluate = getOne(queryWrapper, false);
            queryWrapper.clear();
            return parkEvaluate;
        }, CarParkEvaluate.class);
        return null == carParkEvaluate ? "" : carParkEvaluate.getContent();
    }

    private void check(CarParkEvaluateDTO dto) {
        CarParkEvaluate parkEvaluate = getUniqueBySid(dto.getSid(), dto.getEvaluationLabel());
        if (null != parkEvaluate) {
            log.error("请勿重复评价,eid: {}", parkEvaluate.getEid());
            parkEvaluate.freeData();
            throw new ApiException("请勿重复评价");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveList(CarParkEvaluateDTO dto) {
        if (!StringUtils.hasLength(dto.getUserId())) {
            dto.setUserId(SecurityUtils.getLoginUser().getUserId());
        }
        final UserStrokeHistory userStrokeHistory = Optional.ofNullable(userStrokeHistoryService.getById(dto.getSid())).orElseThrow(() -> new ApiException("历史行程不存在"));
        final String[] lids = dto.getEvaluationLabel().split(";");
        LocalDateTime now = LocalDateTime.now();
        for (String lid : lids) {
            CarParkScoreEvaluationLabelDTO label = Optional.ofNullable(carParkScoreEvaluationLabelService.getByIdDto(lid)).orElseThrow(() -> new ApiException("评价标签ID不存在"));
            if (null == getUniqueBySid(dto.getSid(), dto.getEvaluationLabel())) {
                CarParkEvaluateDTO model = new CarParkEvaluateDTO();
                model.setParkId(dto.getParkId());
                model.setSid(dto.getSid());
                model.setScore(label.getScore());
                model.setContent(label.getContent());
                model.setUserId(dto.getUserId());
                model.setAddTime(now);
                model.setPlateNumber(userStrokeHistory.getPlateNumber());
                model.setEvaluationLabel(label.getLid());
                save(model);
                model.freeData();
            }
        }
    }
}